using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using log4net;
using log4net.Core;
using Quiki.DocumentGraphProcessors;
using Quiki.DocumentPostprocessors;
using Quiki.ExpressionParsers;
using Quiki.Querying;
using Quiki.Services;
using Quiki.Templates;

namespace Quiki
{
    public class DocumentProcessor
    {
        public DocumentProcessor(DocumentProcessorSettings settings, ILogger logger)
        {
            this.settings = settings;
            this.logger = logger;

            ITemplatingEngine templatingEngine = new NVelocityTemplatingEngine(logger);
            IQueryParser queryParser = new QueryParser();

            templatesRegistry = new TemplatesRegistry();
            templatesRegistry.RegisterTemplate(new TitleTemplate(logger));
            templatesRegistry.RegisterTemplate(new SeeAlsoTemplate(templatingEngine, logger, settings));
            templatesRegistry.RegisterTemplate(new ListTemplate(templatingEngine, queryParser, logger, settings));
            templatesRegistry.RegisterTemplate(new SeeAlsoListTemplate(templatingEngine, queryParser, logger, settings));
            templatesRegistry.RegisterTemplate(new NoteTemplate(templatingEngine, logger, settings));
            templatesRegistry.RegisterTemplate(new IndexTemplate(templatingEngine, logger, settings));
            templatesRegistry.RegisterTemplate(new TodoTemplate(templatingEngine, logger, settings));
            templatesRegistry.RegisterTemplate(new IncludeFileTemplate(logger, settings));
            templatesRegistry.RegisterTemplate(new TableOfContentsTemplate());

            documentPostProcessors.Add(new DocumentElementsMerger());
            documentPostProcessors.Add(new ParsingInfoGenerator(settings, logger));
            documentPostProcessors.Add(new CategoriesScooper());
            documentPostProcessors.Add(new DocumentTitleInserter());
            documentPostProcessors.Add(new TocGenerator());

            documentGraphProcessors.Add(new TemplateProcessor(templatesRegistry, logger));
            documentGraphProcessors.Add (new LinkGraphConstructor (logger));
            documentGraphProcessors.Add (new HtmlDocumentsGenerator(
                settings,
                templatesRegistry, 
                documentFiles,
                logger));
            documentGraphProcessors.Add(new CopyImagesProcessor(settings, documentFiles, logger));
            documentGraphProcessors.Add(new CopyLinkedFilesProcessor(settings, documentFiles, logger));

            if (false == string.IsNullOrEmpty(settings.FtpServer))
                documentGraphProcessors.Add(new DocumentFilesUploader(settings, documentFiles, new FtpLibFtpClient(), logger));
        }

        public DocumentGraph DocumentGraph
        {
            get { return documentGraph; }
        }

        public void Process()
        {
            documentFiles.Clear();
            LoadAndParseDocuments();
            ExecuteDocumentGraphProcessors();
        }

        private void ExecuteDocumentGraphProcessors()
        {
            foreach (IDocumentGraphProcessor processor in documentGraphProcessors)
            {
                logger.PushContext(processor.GetType().Name);
                logger.Log(Level.Info, "Starting processor");
                processor.Execute(documentGraph);
                logger.PopContext();
            }
        }

        private void LoadAndParseDocuments()
        {
            LoadAndParseDocuments(settings.InputDir);
        }

        private void LoadAndParseDocuments(string directory)
        {
            string directoryFullPath = Path.GetFullPath(directory);

            string[] fileNames = Directory.GetFiles (directoryFullPath, settings.InputFilter);

            Regex fileExclusionFilterRegex = new Regex (
                settings.FileExclusionFilter, 
                RegexOptions.Compiled | RegexOptions.IgnoreCase);

            foreach (string fileName in fileNames)
            {
                if (fileExclusionFilterRegex.IsMatch (fileName))
                    continue;

                WikiDocument document = ParseDocument(fileName);

                foreach (IDocumentPostprocessor postProcessor in documentPostProcessors)
                    postProcessor.PostProcessDocument(document);
            }

            foreach (string subdirectory in Directory.GetDirectories(directoryFullPath))
            {
                if (fileExclusionFilterRegex.IsMatch (subdirectory))
                    continue;

                string subdirectoryPath = Path.Combine(directory, subdirectory);
                LoadAndParseDocuments (subdirectoryPath);
            }
        }

        private WikiDocument ParseDocument(string fileName)
        {
            string inputDir = Path.GetFullPath(settings.InputDir);
            string relativeFileName = fileName.Substring (inputDir.Length + 1);

            WikiDocReference docReference = GetDocumentReferenceFromFileName(relativeFileName);
            WikiDocument doc = new WikiDocument (docReference, relativeFileName);
            documentGraph.AddDocument(doc);

            ParseFile (doc);

            log.DebugFormat ("Parsed document {0}", doc.Reference);

            return doc;
        }

        private static WikiDocReference GetDocumentReferenceFromFileName(string fileName)
        {
            fileName = fileName.Replace('_', ' ');

            WikiDocReference reference = new WikiDocReference();
            string documentName = Path.GetFileNameWithoutExtension (fileName);

            reference.InsertPartAtFront(documentName);

            string path = Path.GetDirectoryName(fileName);
            while (false == String.IsNullOrEmpty(path))
            {
                string dirName = Path.GetFileName(path);
                reference.InsertPartAtFront(dirName);
                path = Path.GetDirectoryName(path);
            }

            return reference;
        }

        private void ParseFile(WikiDocument document)
        {
            log.InfoFormat ("Parsing document '{0}'", document.Reference);

            string fullPath = Path.Combine (settings.InputDir, document.FileName);

            string wikiText = File.ReadAllText(fullPath);
            WikiTextParser parser = new WikiTextParser ();
            WikiTextElement rootElement = parser.Parse (wikiText);

            document.RootElement = rootElement;

            LookForCustomTitle(document, document.RootElement);
        }

        private static bool LookForCustomTitle(WikiDocument document, WikiTextElement element)
        {
            if (element.ElementType == WikiTextElementType.Template)
            {
                WikiTemplateElement templateElement = (WikiTemplateElement) element;

                if (templateElement.TemplateName == TitleTemplate.TemplateId)
                {
                    document.SetCustomTitle (templateElement.TemplateParameters[0]);
                    return true;
                }
            }

            foreach (WikiTextElement childElement in element.ChildElements)
            {
                if (LookForCustomTitle(document, childElement))
                    return true;
            }

            return false;
        }

        private DocumentGraph documentGraph = new DocumentGraph();
        private static readonly ILog log = LogManager.GetLogger(typeof(DocumentProcessor));
        private readonly DocumentProcessorSettings settings;
        private readonly ILogger logger;
        private List<IDocumentPostprocessor> documentPostProcessors = new List<IDocumentPostprocessor> ();
        private List<IDocumentGraphProcessor> documentGraphProcessors = new List<IDocumentGraphProcessor>();
        private TemplatesRegistry templatesRegistry;
        private FileSet documentFiles = new FileSet();
    }
}